home *** CD-ROM | disk | FTP | other *** search
- (c) Copyright 1989-1999 Amiga, Inc. All rights reserved.
- The information contained herein is subject to change without notice, and
- is provided "as is" without warranty of any kind, either expressed or implied.
- The entire risk as to the use of this information is assumed by the user.
-
-
-
- 8SVX: Playing Samples Larger Than 128K
-
- by Dan Baker
-
-
- The Amiga's audio hardware contains four digital-to-analog converters
- (DACs) capable of playing back digital sound samples with 8-bit resolution.
- On the Amiga sound samples are usually stored in the 8SVX format which is
- the IFF file standard for 8-bit samples. The 8SVX standard allows samples
- up to 2 gigabytes long. However, the length registers of the DACs only
- allow samples up to 128K bytes. In order to play back a sample larger than
- 128K, you break it up into smaller pieces and send multiple requests to the
- audio device. The program listed below shows you how to use this technique.
-
- The register map in Appendix B of the Hardware Manual shows that all the
- audio length registers are 16 bits which gives a maximum length of 65,536.
- However, since the audio hardware can DMA two bytes at a time, the length
- register is set up to represent the word count of the sample, not the
- byte count. Hence, the maximum length for a sample is 2 times 65,536 or
- 128K bytes.
-
- Audio sample data in the 8SVX format is stored as a standard IFF Chunk
- of type BODY. The Chunk size is given by the field ckSize which is a
- LONG variable. So the maximum sample size in an IFF Chunk is just over
- 2 gigabytes. Even larger samples might be possible by using a CAT
- or LIST. See the IFF manual for more details.
-
- The original Amiga had 512K of chip RAM, much of which would be busy doing
- graphics work, so the 128K audio sample size limit was a good design decision.
- Because of memory limits, most early Amiga samples were short sound
- effects less than 128K.
-
- But the newest Amigas have 1 megabyte of chip RAM and sometimes additional
- expansion RAM. This trend will probably continue as memory prices fall.
- So the idea of supporting 2 gigabyte samples under IFF is not far-fetched.
-
- The program listed below shows you how to play back 8SVX samples larger
- than 128K on the Amiga by breaking up the sample into smaller pieces.
- The program divides large samples into sections of 51200 bytes.
- The samples are sent to the audio device using double-buffering. That is,
- there are always two IO requests in the queue. A Wait(), GetMsg() loop puts
- the progam to sleep until the current audio request finishes.
-
- When the program wakes up, the next request in the queue begins to play
- immediately. While it plays, new sample data is copied into chip memory.
- The request that just ended is then reused by placing it back in the queue
- and the program goes through the Wait(), GetMsg() loop again. Note that
- there is nothing special about using 51200-byte samples. Any size up to
- 128K would work but 51200 is used to conserve chip memory.
-
- The IFF parsing used by the program is very simple. The FORM is first
- inspected for the file size and the file is read into memory. A switch
- statement is used to cull the VHDR and BODY Chunks from the file and
- pointers are set up. Other Chunks are skipped. CATs, LISTs and nested
- FORMs are not supported. If the BODY Chunk has both a one-shot and
- continuous part, only the one-shot part is used. Similarly, only
- the first ocatve part is played in a multi-octave sample.
-
- The audio device is handled in the usual way. Two AudioIO requests and a
- reply port are set up. Only one audio channel is used. The channel is
- allocated automatically when the device is opened. The allocation key is
- set to accept any of the four channels. The request priority is set to 128
- so that once we have the channel, it cannot be stolen by another task during
- playback.
-
- An important part of the program is the setting of the clock constant to
- the right value for both PAL and NTSC systems. The clock constant, which is
- used in calculating the period of an audio request, is different on PAL and
- NTSC systems. To get the right value look at the DisplayFlags field of
- GfxBase in the graphics library.
-
- By taking adavantage of the audio device's ability to queue up multiple
- IO requests, it is possible to extend the effective sample size limit of
- the Amiga beyond the 128K barrier. The Amiga's DMA driven audio hardware
- can smoothly play back samples of any arbitrary size.
-
-
- /*----------------*/
- /* INCLUDES */
- /*----------------*/
- #include "exec/types.h"
- #include "exec/memory.h"
- #include "devices/audio.h"
- #include "libraries/dos.h"
- #include "libraries/dosextens.h"
- #include "graphics/gfxbase.h"
- #include "iff/iff.h"
- #include "iff/8svx.h"
-
- #define VHDR MakeID('V','H','D','R')
- #define BODY MakeID('B','O','D','Y')
- #define MY8S MakeID('8','S','V','X')
-
- APTR OpenLibrary();
- struct FileHandle *Open();
- ULONG Read();
- APTR AllocMem();
- struct GfxBase *GfxBase;
- struct MsgPort *CreatePort();
- struct Message *GetMsg();
- struct Task *FindTask();
- BYTE SetTaskPri();
- ULONG OpenDevice();
- ULONG Wait();
-
- void kill8svx();
- void kill8();
-
- /*--------------------*/
- /* G L O B A L S */
- /*--------------------*/
- struct IOAudio *AudioIOBptr1, /* Pointers to Audio IOBs */
- *AudioIOBptr2,
- *Aptr;
- struct Message *msg; /* Msg, port and device for */
- struct MsgPort *port; /* driving audio */
- ULONG device;
- UBYTE *sbase,*fbase; /* For sample memory allocation */
- ULONG fsize,ssize; /* and freeing */
- struct FileHandle *v8handle;
- UBYTE chan1[] = { 1 }; /* Audio channel allocation arrays */
- UBYTE chan2[] = { 2 };
- UBYTE chan3[] = { 4 };
- UBYTE chan4[] = { 8 };
- UBYTE *chans[] = {chan1,chan2,chan3,chan4};
-
- /*-----------*/
- /* M A I N */
- /*-----------*/
- LONG
- main(argc,argv)
- LONG argc;
- char *argv[];
- {
- /*-------------*/
- /* L O C A L S */
- /*-------------*/
- char *fname; /* File name and data pointer*/
- UBYTE *p8data; /* for file read. */
- ULONG clock; /* Clock constant */
- ULONG length[2]; /* Sample lengths */
- BYTE iobuffer[8], /* Buffer for 8SVX header */
- *psample[2]; /* Sample pointers */
- Chunk *p8Chunk; /* Pointers for 8SVX parsing */
- Voice8Header *pVoice8Header;
- ULONG x,y,rd8count,speed; /* Counters, sampling speed */
- ULONG wakebit; /* A wakeup mask */
- BYTE oldpri,c; /* Stuff for bumping priority */
- struct Task *mt;
-
-
- /*-------------*/
- /* C O D E */
- /*-------------*/
-
- /*------------------------------*/
- /* Check Arguments, Initialize */
- /*------------------------------*/
- fbase=0L;sbase=0L;
- AudioIOBptr1=0L;AudioIOBptr2=0L;
- port=0L;v8handle=0L;device=1L;
-
-
- if(argv[1]==NULL) {kill8svx("No file name given.\n");return(1L);}
- fname=argv[1];
-
- /*---------------------------*/
- /* Initialize Clock Constant */
- /*---------------------------*/
- GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",0L);
- if(GfxBase==0L){puts("Can't open graphics library\n");return(1L);}
- if(GfxBase->DisplayFlags & PAL) clock=3546895L; /* PAL clock */
- else clock=3579545L; /* NTSC clock */
- if(GfxBase)CloseLibrary(GfxBase);
-
-
- /*---------------*/
- /* Open the File */
- /*---------------*/
- v8handle=Open(fname,MODE_OLDFILE);
- if(v8handle==0)
- { kill8svx("Can't open 8SVX file.\n"); return(1L); }
-
- /*-------------------------------------------*/
- /* Read the 1st 8 Bytes of the File for Size */
- /*-------------------------------------------*/
- rd8count=Read(v8handle,iobuffer,8);
- if(rd8count==-1){kill8svx ("Read error.\n");return(1L);}
- if(rd8count<8) {kill8svx ("Not an IFF 8SVX file, too short\n");return(1L);}
-
- /*-----------------*/
- /* Evaluate Header */
- /*-----------------*/
- p8Chunk=(Chunk *)iobuffer;
- if( p8Chunk->ckID != FORM ) {kill8svx("Not an IFF FORM.\n");return(1L);}
-
- /*--------------------------*/
- /* Allocate Memory for File */
- /* and Read it in. */
- /*--------------------------*/
- fbase= (UBYTE *)AllocMem(fsize=p8Chunk->ckSize , MEMF_PUBLIC|MEMF_CLEAR);
- if(fbase==0) {kill8svx("No memory for read.\n");return(1L);}
- p8data=fbase;
-
- rd8count=Read(v8handle,p8data,p8Chunk->ckSize);
- if(rd8count==-1)
- {kill8svx ("Read error.\n");return(1L);}
- if(rd8count<p8Chunk->ckSize)
- {kill8svx ("Malformed IFF, too short.\n");return(1L);}
- /*-------------------*/
- /* Evaluate IFF Type */
- /*-------------------*/
- if(MakeID( *p8data, *(p8data+1) , *(p8data+2) , *(p8data+3) ) != MY8S )
- {kill8svx("Not an IFF 8SVX file.\n");return(1L);}
-
- /*----------------------*/
- /* Evaluate 8SVX Chunks */
- /*----------------------*/
-
- p8data=p8data+4;
-
- while( p8data < fbase+fsize )
- {
- p8Chunk=(Chunk *)p8data;
-
- switch(p8Chunk->ckID)
- {
- case VHDR:
- /*------------------------------------------------*/
- /* Get a pointer to the 8SVX header for later use */
- /*------------------------------------------------*/
- pVoice8Header=(Voice8Header *)(p8data+8L);
- break;
- case BODY:
-
- /*-------------------------------------------------*/
- /* Create pointers to 1-shot and continuous parts */
- /* for the top octave and get length. Store them. */
- /*-------------------------------------------------*/
- psample[0] = (BYTE *)(p8data + 8L);
- psample[1] = psample[0] + pVoice8Header->oneShotHiSamples;
- length[0] = (ULONG)pVoice8Header->oneShotHiSamples;
- length[1] = (ULONG)pVoice8Header->repeatHiSamples;
- break;
-
- default:
- break;
- }
-
- /* end switch */
-
- p8data =p8data + 8L + p8Chunk->ckSize;
-
- if(p8Chunk->ckSize&1L ==1) p8data++;
- }
-
- /* Play either the one-shot or continuous, not both */
- if (length[0]==0) y=1;
- else y=0;
-
- /*---------------------------------------*/
- /* Allocate chip memory for samples and */
- /* copy from read buffer to chip memory. */
- /*---------------------------------------*/
- if(length[y]<=102400)ssize=length[y];
- else ssize=102400;
-
- sbase=(UBYTE *)AllocMem( ssize , MEMF_CHIP | MEMF_CLEAR);
- if(sbase==0) {kill8svx("No chip memory.\n");return(1L);}
- CopyMem(psample[y],sbase,ssize);
- psample[y]+=ssize;
-
- /*----------------------------------*/
- /* Calculate playback sampling rate */
- /*----------------------------------*/
- speed = clock / pVoice8Header->samplesPerSec;
-
- /*-------------------*/
- /* Bump our priority */
- /*-------------------*/
- mt=FindTask(NULL);
- oldpri=SetTaskPri(mt,21);
-
- /*---------------------------------------------*/
- /* Allocate audio I/O blocks and make a port */
- /*---------------------------------------------*/
- AudioIOBptr1=(struct IOAudio *)
- AllocMem( sizeof(struct IOAudio),MEMF_CHIP|MEMF_PUBLIC|MEMF_CLEAR);
- if(AudioIOBptr1==0) {kill8svx("No IO memory\n");return(1L);}
-
- AudioIOBptr2=(struct IOAudio *)
- AllocMem( sizeof(struct IOAudio),MEMF_CHIP|MEMF_PUBLIC|MEMF_CLEAR);
- if(AudioIOBptr2==0) {kill8svx("No IO memory\n");return(1L);}
-
- port=CreatePort(0,0);
- if(port==0){kill8svx("No port\n"); return(1L);}
- c=0;
-
- while(device!=0)
- {
- /*---------------------------------------*/
- /* Set up audio I/O block for channel */
- /* allocation and Open the audio device */
- /*---------------------------------------*/
- AudioIOBptr1->ioa_Request.io_Message.mn_ReplyPort = port;
- AudioIOBptr1->ioa_Request.io_Message.mn_Node.ln_Pri = 128;
- AudioIOBptr1->ioa_AllocKey = 0;
- AudioIOBptr1->ioa_Data = chans[c];
- AudioIOBptr1->ioa_Length = 1;
-
- device=OpenDevice("audio.device",0,AudioIOBptr1,0);
- c++;
- }
- if(device!=0){kill8svx("No channel\n"); return(1L);}
-
- /*-------------------------------------------*/
- /* Set Up Audio IO Blocks for Sample Playing */
- /*-------------------------------------------*/
- AudioIOBptr1->ioa_Request.io_Command =CMD_WRITE;
- AudioIOBptr1->ioa_Request.io_Flags =ADIOF_PERVOL;
-
- /*--------*/
- /* Volume */
- /*--------*/
- AudioIOBptr1->ioa_Volume=60;
- /*---------------*/
- /* Period/Cycles */
- /*---------------*/
- AudioIOBptr1->ioa_Period =(UWORD)speed;
- AudioIOBptr1->ioa_Cycles =1;
- *AudioIOBptr2 = *AudioIOBptr1;
- /*--------*/
- /* Data */
- /*--------*/
- AudioIOBptr1->ioa_Data =(UBYTE *)sbase;
- AudioIOBptr2->ioa_Data =(UBYTE *)sbase + 51200;
-
-
-
- Aptr=AudioIOBptr2;
- /*-----------------*/
- /* Run the sample */
- /*-----------------*/
- if(length[y]<=102400)
- {
- AudioIOBptr1->ioa_Length=length[y]; /* No double buffering needed */
- BeginIO(AudioIOBptr1); /* Begin the sample, wait for */
- wakebit=0L; /* it to finish, then quit. */
- wakebit=Wait(1 << port->mp_SigBit);
- msg=GetMsg(port);
- }
- else
- {
- length[y]-=102400; /* It's a real long sample so */
- AudioIOBptr1->ioa_Length=51200L; /* double buffering is needed */
- AudioIOBptr2->ioa_Length=51200L;
- BeginIO(AudioIOBptr1); /* Queue up two samples and Wait */
- BeginIO(AudioIOBptr2); /* for the first to finish. */
- while(length[y]>0) /* Reuse the Audio IOB, queue it */
- { /* up again and wait for the 2nd */
- wakebit=Wait(1 << port->mp_SigBit); /* Audio IOB to finish. Reuse */
- msg=GetMsg(port); /* the 2nd, queue it up, repeat. */
-
- if(Aptr==AudioIOBptr1)Aptr=AudioIOBptr2;
- else Aptr=AudioIOBptr1;
-
- if(length[y]<=51200) Aptr->ioa_Length=length[y];
- else Aptr->ioa_Length=51200L;
-
- CopyMem(psample[y],Aptr->ioa_Data,Aptr->ioa_Length);
-
- length[y]-=Aptr->ioa_Length;
- psample[y]+=51200;
- BeginIO(Aptr);
- }
- wakebit=Wait(1 << port->mp_SigBit);
- msg=GetMsg(port);
- wakebit=Wait(1 << port->mp_SigBit);
- msg=GetMsg(port);
- }
- kill8();
- return(0L);
-
- }
-
-
- /*----------------*/
- /* Abort the Read */
- /*----------------*/
- void
- kill8svx(kill8svxstring)
- char *kill8svxstring;
- {
- puts(kill8svxstring);
- kill8();
- }
- /*-------------------------*/
- /* Return system resources */
- /*-------------------------*/
- void
- kill8()
- {
- if(v8handle!=0)Close(v8handle);
- if(fbase !=0) FreeMem(fbase,fsize);
- if(sbase !=0) FreeMem (sbase, ssize);
-
- if(device ==0) CloseDevice(AudioIOBptr1);
- if( port !=0) DeletePort(port);
- if(AudioIOBptr1!=0) FreeMem( AudioIOBptr1,sizeof(struct IOAudio) );
- if(AudioIOBptr2!=0) FreeMem( AudioIOBptr2,sizeof(struct IOAudio) );
- }
-